#!/usr/bin/env python

"""Locate Visual Studio installations with Visual Studio Setup Configuration utility DLL"""

import os
import ctypes


def get_vs_installations():
    class ISetupInstanceVTable(ctypes.Structure):
        """Class matching VisualStudio Setup package ISetupInstance vtable"""

        pass

    class ISetupInstance(ctypes.Structure):
        """COM interface for ISetupInstance"""

        _fields_ = [("vtable", ctypes.POINTER(ISetupInstanceVTable))]

    class IEnumSetupInstancesVTable(ctypes.Structure):
        """Class matching VisualStudio Setup package IEnumSetupInstances vtable"""

        pass

    class IEnumSetupInstances(ctypes.Structure):
        """COM interface for IEnumSetupInstances"""

        _fields_ = [("vtable", ctypes.POINTER(IEnumSetupInstancesVTable))]

    class ISetupConfigurationVTable(ctypes.Structure):
        """Class matching VisualStudio Setup package ISetupConfiguration vtable"""

        pass

    class ISetupConfiguration(ctypes.Structure):
        """COM interface for ISetupConfiguration"""

        _fields_ = [("vtable", ctypes.POINTER(ISetupConfigurationVTable))]

    proto_get_installation_path = ctypes.WINFUNCTYPE(
        ctypes.c_int, ctypes.POINTER(ISetupInstance), ctypes.POINTER(ctypes.c_wchar_p)
    )

    proto_get_installation_version = ctypes.WINFUNCTYPE(
        ctypes.c_int, ctypes.POINTER(ISetupInstance), ctypes.POINTER(ctypes.c_wchar_p)
    )

    ISetupInstanceVTable._fields_ = (
        ("QueryInterface", ctypes.c_void_p),
        ("AddRef", ctypes.c_void_p),
        ("Release", ctypes.c_void_p),
        ("GetInstanceId", ctypes.c_void_p),
        ("GetInstallDate", ctypes.c_void_p),
        ("GetInstallationName", ctypes.c_void_p),
        ("GetInstallationPath", proto_get_installation_path),
        ("GetInstallationVersion", proto_get_installation_version),
        ("GetDisplayName", ctypes.c_void_p),
        ("GetDescription", ctypes.c_void_p),
        ("ResolvePath", ctypes.c_void_p),
    )

    proto_next = ctypes.WINFUNCTYPE(
        ctypes.c_int,
        ctypes.POINTER(IEnumSetupInstances),
        ctypes.c_int,
        ctypes.POINTER(ctypes.POINTER(ISetupInstance)),
        ctypes.POINTER(ctypes.c_int),
    )

    IEnumSetupInstancesVTable._fields_ = (
        ("QueryInterface", ctypes.c_void_p),
        ("AddRef", ctypes.c_void_p),
        ("Release", ctypes.c_void_p),
        ("Next", proto_next),
        ("Skip", ctypes.c_void_p),
        ("Reset", ctypes.c_void_p),
        ("Clone", ctypes.c_void_p),
    )

    proto_enum_instances = ctypes.WINFUNCTYPE(
        ctypes.c_int,
        ctypes.POINTER(ISetupConfiguration),
        ctypes.POINTER(ctypes.POINTER(IEnumSetupInstances)),
    )

    ISetupConfigurationVTable._fields_ = (
        ("QueryInterface", ctypes.c_void_p),
        ("AddRef", ctypes.c_void_p),
        ("Release", ctypes.c_void_p),
        ("EnumInstances", proto_enum_instances),
        ("GetInstanceForCurrentProcess", ctypes.c_void_p),
        ("GetInstanceForPath", ctypes.c_void_p),
    )

    proto_get_setup_configuration = ctypes.WINFUNCTYPE(
        ctypes.c_int,
        ctypes.POINTER(ctypes.POINTER(ISetupConfiguration)),
        ctypes.c_void_p,
    )

    installations = []
    dll = None

    dll_path = os.path.expandvars(
        "$ProgramData\\Microsoft\\VisualStudio\\Setup\\x64\\Microsoft.VisualStudio.Setup.Configuration.Native.dll"
    )
    try:
        dll = ctypes.WinDLL(dll_path)
    except OSError as e:
        # print("Failed to load Visual Studio setup configuration DLL: " + str(e))
        return installations

    params_get_setup_configuration = (
        (1, "configuration", 0),
        (1, "reserved", 0),
    )

    get_setup_configuration = proto_get_setup_configuration(
        ("GetSetupConfiguration", dll), params_get_setup_configuration
    )

    configuration = ctypes.POINTER(ISetupConfiguration)()
    reserved = ctypes.c_void_p(0)

    result = get_setup_configuration(ctypes.byref(configuration), reserved)
    if result != 0:
        # print("Failed to get setup configuration: " + str(result))
        return installations

    enum_instances = configuration.contents.vtable.contents.EnumInstances

    enum_setup_instances = ctypes.POINTER(IEnumSetupInstances)()
    result = enum_instances(configuration, ctypes.byref(enum_setup_instances))
    if result != 0:
        # print("Failed to enum setup instances: " + str(result))
        return installations

    setup_instance = ctypes.POINTER(ISetupInstance)()
    fetched = ctypes.c_int(0)

    while True:
        next = enum_setup_instances.contents.vtable.contents.Next
        result = next(
            enum_setup_instances, 1, ctypes.byref(setup_instance), ctypes.byref(fetched)
        )
        if result == 1 or fetched == 0:
            break
        if result != 0:
            # print("Failed to get next setup instance: " + str(result))
            break

        version = ctypes.c_wchar_p()
        path = ctypes.c_wchar_p()

        get_installation_version = (
            setup_instance.contents.vtable.contents.GetInstallationVersion
        )
        get_installation_path = (
            setup_instance.contents.vtable.contents.GetInstallationPath
        )

        result = get_installation_version(setup_instance, ctypes.byref(version))
        if result != 0:
            # print("Failed to get setup instance version: " + str(result))
            break

        result = get_installation_path(setup_instance, ctypes.byref(path))
        if result != 0:
            # print("Failed to get setup instance version: " + str(result))
            break

        installations.append((version.value, path.value))

    return installations


if __name__ == "__main__":

    installations = get_vs_installations()

    for version, path in installations:
        print(version + " " + path)
